%{
#include <stdlib.h>
#include <stdio.h>
#include "symbol_table.h"
#include "ast.h"
#include "tac.h"
#include "analyzer.h"

int getLineNumber(void);

// Abstract Syntaxe Tree Root Node
AST* root;

%}

/* tokens for var types (leaf nodes)  */
%token KW_INT
%token KW_BYTE

/* tokens for control flow */
%token KW_IF
%token KW_THEN
%token KW_ELSE
%token KW_WHILE

/* tokens for commands */
%token KW_PRINT
%token KW_RETURN
%token KW_READ

/* tokens for operators */
%token OPERATOR_L
%token OPERATOR_G
%token OPERATOR_LE
%token OPERATOR_GE
%token OPERATOR_EQ
%token OPERATOR_NE
%token OPERATOR_AND
%token OPERATOR_OR

/* token for identifier */
%token<symbol> TK_IDENTIFIER

/* tokens for literals */
%token<symbol> LIT_CHAR
%token<symbol> LIT_INTEGER
%token<symbol> LIT_STRING

%token TOKEN_ERROR   290

%type<node> program
%type<node> declaration_list
%type<node> declaration
%type<node> param
%type<node> param_list
%type<node> type
%type<node> cmd_list
%type<node> cmd_rest
%type<node> cmd
%type<node> cmd_block
%type<node> expr
%type<node> func_call_param
%type<node> func_call_param_list
%type<node> func_call_param_rest


/* Intermediate nodes */

%nonassoc '!'
%left OPERATOR_AND OPERATOR_OR
%left OPERATOR_LE OPERATOR_GE OPERATOR_EQ OPERATOR_NE OPERATOR_L OPERATOR_G
%left '+' '-'
%left '*' '/'

%union {
    HASH_NODE* symbol;
    AST* node;
}

%start program

%%

/* Production rule for the program */
program: declaration_list                                                       {
                                                                                    root = $1;
                                                                                    int check_duplication = analyzer_check_for_duplicated_identifiers(root);
                                                                                    int check_declarations = analyzer_check_for_not_declared_identifier(table);
                                                                                    int check_usage = analyzer_check_identifier_use(root);

                                                                                    if (!check_duplication &&
                                                                                        !check_declarations &&
                                                                                        !check_usage) {
                                                                                        printf("[COMPILER] generating code...\n");
                                                                                        TAC* tac_code = ast_generate_tac_code(root);

                                                                                        tac_reverse(tac_code);
                                                                                        tac_code = tac_get_first(tac_code);
                                                                                        //tac_print(tac_code);
                                                                                        code_gen(tac_code, table, "assembly.s");
                                                                                    } else {
                                                                                        printf("[COMPILER] There are errors in your code. Fix them and try again.\n");
                                                                                    }
                                                                                }
    ;

declaration_list: declaration declaration_list                                  { $$ = ast_create(AST_DEFS, NULL, NULL, $1, $2, NULL, NULL, getLineNumber()); }
    |                                                                           { $$ = NULL; }
    ;

declaration: type TK_IDENTIFIER ';'                                             { $2->data->ast_declaration_node = ast_create(AST_VAR_DEF, $2, NULL, $1, NULL, NULL, NULL, getLineNumber());  $$ = $2->data->ast_declaration_node; }
    | type TK_IDENTIFIER '[' LIT_INTEGER ']' ';'                                { $2->data->ast_declaration_node = ast_create(AST_VEC_DEF, $2, $4, $1, NULL, NULL, NULL, getLineNumber()); $$ = $2->data->ast_declaration_node; }
    | type TK_IDENTIFIER '(' param_list ')' cmd_block                           { $2->data->ast_declaration_node = ast_create(AST_FUNC_DEF, $2, NULL, $1, $4, $6, NULL, getLineNumber()); $$ = $2->data->ast_declaration_node; }
    ;

param: type TK_IDENTIFIER                                                       { $$ = ast_create(AST_PARAM, $2, NULL, $1, NULL, NULL, NULL, getLineNumber()); }
	;

param_list: param param_list                                                    { $$ = ast_create(AST_PARAM_LIST, NULL, NULL, $1, $2, NULL, NULL, getLineNumber()); }
	|                                                                           { $$ = NULL; }
	;

/* Production rule for variable types */
type: KW_INT                                                                    { $$ = ast_create(AST_INT_TYPE, NULL, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
	| KW_BYTE                                                                   { $$ = ast_create(AST_BYTE_TYPE, NULL, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
	;

cmd_list: cmd cmd_rest                                                          { $$ = ast_create(AST_LCMD, NULL, NULL, $1, $2, NULL, NULL, getLineNumber()); }
    |                                                                           { $$ = NULL; }
    ;

cmd_rest: ';' cmd_list                                                          { $$ = $2; }
    |                                                                           { $$ = NULL; }
    ;

cmd: TK_IDENTIFIER '=' expr                                                     { $$ = ast_create(AST_ASS, $1, NULL, $3, NULL, NULL, NULL, getLineNumber()); }
    | TK_IDENTIFIER '[' expr ']' '=' expr                                       { $$ = ast_create(AST_ASS_VEC, $1, NULL, $3, $6, NULL, NULL, getLineNumber()); }
    | KW_IF '(' expr ')' KW_THEN cmd                                            { $$ = ast_create(AST_IF, NULL, NULL, $3, $6, NULL, NULL, getLineNumber()); }
    | KW_IF '(' expr ')' KW_THEN cmd KW_ELSE cmd                                { $$ = ast_create(AST_IF_ELSE, NULL, NULL, $3, $6, $8, NULL, getLineNumber()); }
    | KW_WHILE '(' expr ')' cmd                                                 { $$ = ast_create(AST_WHILE, NULL, NULL, $3, $5, NULL, NULL, getLineNumber()); }
    | cmd_block                                                                 { $$ = $1; }
    | KW_PRINT expr                                                             { $$ = ast_create(AST_PRINT_EXPR, NULL, NULL, $2, NULL, NULL, NULL, getLineNumber()); }
    | KW_PRINT LIT_STRING                                                       { $$ = ast_create(AST_PRINT_STRING, $2, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
    | KW_RETURN expr                                                            { $$ = ast_create(AST_RETURN, NULL, NULL, $2, NULL, NULL, NULL, getLineNumber()); }
    | KW_READ TK_IDENTIFIER                                                     { $$ = ast_create(AST_READ, $2, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
    ;

cmd_block: '{' cmd_list '}'                                                     { $$ = ast_create(AST_CMD_BLOCK, NULL, NULL, $2, NULL, NULL, NULL, getLineNumber()); }
    ;

expr: expr '+' expr                                                             { $$ = ast_create(AST_ADD, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr '-' expr                                                             { $$ = ast_create(AST_SUB, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr '*' expr                                                             { $$ = ast_create(AST_MUL, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr '/' expr                                                             { $$ = ast_create(AST_DIV, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | '(' expr ')'                                                              { $$ = ast_create(AST_EXPR_PAR, NULL, NULL, $2, NULL, NULL, NULL, getLineNumber()); }
    | TK_IDENTIFIER                                                             { $$ = ast_create(AST_IDENTIFIER, $1, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
    | TK_IDENTIFIER '[' expr ']'                                                { $$ = ast_create(AST_VEC_REF, $1, NULL, $3, NULL, NULL, NULL, getLineNumber()); }
    | LIT_INTEGER                                                               { $$ = ast_create(AST_LIT_INT, $1, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
    | LIT_CHAR                                                                  { $$ = ast_create(AST_LIT_CHAR, $1, NULL, NULL, NULL, NULL, NULL, getLineNumber()); }
    | '!' expr                                                                  { $$ = ast_create(AST_OPERATOR_NOT, NULL, NULL, $2, NULL, NULL, NULL, getLineNumber()); }
    | TK_IDENTIFIER '(' func_call_param_list ')'                                { $$ = ast_create(AST_FUNC_CALL, $1, NULL, $3, NULL, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_OR expr                                                     { $$ = ast_create(AST_OPERATOR_OR, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_AND expr                                                    { $$ = ast_create(AST_OPERATOR_AND, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_LE expr                                                     { $$ = ast_create(AST_OPERATOR_LE, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_G expr                                                      { $$ = ast_create(AST_OPERATOR_G, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_L expr                                                      { $$ = ast_create(AST_OPERATOR_L, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_GE expr                                                     { $$ = ast_create(AST_OPERATOR_GE, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_EQ expr                                                     { $$ = ast_create(AST_OPERATOR_EQ, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    | expr OPERATOR_NE expr                                                     { $$ = ast_create(AST_OPERATOR_NE, NULL, NULL, $1, $3, NULL, NULL, getLineNumber()); }
    ;

func_call_param: expr                                                           { $$ = $1; }
    ;

func_call_param_list: func_call_param func_call_param_rest                      { $$ = ast_create(AST_FUNC_PARAM_LIST, NULL, NULL, $1, $2, NULL, NULL, getLineNumber()); }
	|                                                                           { $$ = NULL; }
	;

func_call_param_rest: ',' func_call_param_list                                  { $$ = $2; }
    |                                                                           { $$ = NULL; }
    ;

%%

int yyerror(char *s) {
    fprintf(stderr, "%s on line: %d\n", s, getLineNumber());

	// end the program with an error code.
	exit(3);
}
